package com.turing.data.service.review.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.turing.common.entity.Context;
import com.turing.common.response.ResponseVO;
import com.turing.common.entity.ParseTableVO;
import com.turing.common.util.*;
import com.turing.data.constants.EleType;
import com.turing.data.constants.GroupType;
import com.turing.data.constants.NbConstant;
import com.turing.data.constants.TableType;
import com.turing.data.dao.review.ReviewContentMapper;
import com.turing.data.engine.M2BEngine;
import com.turing.data.inner.DrawTaskInnerService;
import com.turing.data.inner.FileTaskInnerService;
import com.turing.data.model.domain.element.*;
import com.turing.data.model.domain.fileTask.FileTaskVo;
import com.turing.data.model.domain.review.ModelContent;
import com.turing.data.model.domain.review.ReviewContent;
import com.turing.data.service.element.*;
import com.turing.data.service.excel.ExportFieldService;
import com.turing.data.service.excel.ExportInfoService;
import com.turing.data.service.review.ModelContentService;
import com.turing.data.service.review.ReviewOperationService;
import com.turing.data.service.review.ReviewService;
import com.turing.data.utils.DuplicateUtil;
import com.turing.data.utils.XwpfTUtil;
import com.turing.data.vo.in.review.ElementSimpleSearchVO;
import com.turing.data.vo.in.review.ScrollSearchVO;
import com.turing.data.vo.out.review.DependParamsVO;
import com.turing.data.vo.out.review.DrawTaskVO;
import com.turing.data.vo.out.review.TableVO;
import com.turing.data.vo.out.review.TaskElementVO;
import com.turing.orm.handler.DateTableNameParser;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.swing.text.html.Option;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class ReviewServiceImpl extends ServiceImpl<ReviewContentMapper, ReviewContent> implements ReviewService {

    @Value("${fdfs.domain}")
    private String FDFS_FILE_DOMAIN;


    @Value("${template-path}")
    private String TEMPLATE_PATH;

    @Autowired
    private ReviewContentMapper reviewContentMapper;

    @Autowired
    private FileTaskInnerService fileTaskInnerService;

    @Autowired
    private ElementService elementService;


    @Autowired
    private ReviewOperationService reviewOperationService;

    @Autowired
    private BusiBusiEleRelationService busiBusiEleRelationService;

    @Autowired
    private BusiModelRelationService busiModelRelationService;

    @Autowired
    private ElementGroupService elementGroupService;

    @Autowired
    private ModelContentService modelContentService;

    /**
     * 场景feign接口
     */
    @Autowired
    private TemplateService templateService;

    @Autowired
    private DrawTaskInnerService drawTaskInnerService;

    @Resource
    private ExportInfoService exportInfoService;


    @Resource
    private ExportFieldService ExportFieldService;

    @Autowired
    private ReviewService reviewService;

    private Log logger = LogFactory.getLog(ReviewServiceImpl.class);

    @Override
    public void exportData(String taskSn, String templateId, HttpServletResponse response) {

        List<ExcelExportEntity> excelExportEntities = new ArrayList<>();
        List<Object> data = new ArrayList<>();
        HashMap<Object, Object> map = new HashMap<>();

        ExportParams exportParams = new ExportParams();
        exportParams.setSheetName(templateService.getTemplateNameById(templateId));
        exportParams.setType(ExcelType.XSSF);

        List<Element> elements = elementService.getElementsByTemplateId(templateId, 1);
        if (Tools.isNotEmpty(elements)) {
            for (Element element : elements) {

                DateTableNameParser.setTaskSn(Long.parseLong(taskSn));

                List<ReviewContent> reviewContents = new LambdaQueryChainWrapper<>(reviewContentMapper).eq(ReviewContent::getTaskSn, taskSn).eq(ReviewContent::getEleId, element.getId()).list();

                List<TaskElementVO.DrawResultVO> drawResultVOS = transfer2DrawResultList(reviewContents, false);
                excelExportEntities.add(new ExcelExportEntity(element.getName(), element.getName(), 40));

                if (Tools.isNotEmpty(drawResultVOS)) {
                    StringBuilder sb = new StringBuilder();
                    drawResultVOS.forEach(
                            d -> {
                                if (StringUtils.isNotEmpty(d.getBusinessValue())) {
                                    sb.append(d.getBusinessValue()).append("、");
                                }
                            }
                    );
                    String str = sb.toString();
                    if (str.length() > 0) {
                        str = str.substring(0, str.length() - 1);
                    }
                    map.put(element.getName(), str);
                } else {
                    map.put(element.getName(), "");
                }
            }
        }
        data.add(map);
        resetCellMaxTextLength();
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, excelExportEntities, data);
        try {
            OutputStream out = Files.newOutputStream(Paths.get("D:\\tmp\\compare\\导出模板.xlsx"));
            workbook.write(out);
        } catch (Exception e) {
            e.printStackTrace();
        }
        Template template = templateService.getTemplateById(templateId);
        try {
            ExcelUtils.export(template.getTemplateName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")), "xlsx", workbook, response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


//    /**
//     * 新股多值导出
//     * @param taskId 任务id
//     * @param templateId 场景id
//     * @param response 前端响应
//     */
//    public void doExport(String taskId,String templateId,HttpServletResponse response){
//
//        //表头列表
//        List<List<ExcelExportEntity>> excelEntityList = new ArrayList<>();
//        List<String> titles = new ArrayList<>();
//
//        ExportInfo exportInfo = exportInfoService.getByTemplateId(templateId);
//        List<Element> elements = elementService.getElementsByTemplateId(templateId,1);
//        if (exportInfo == null || exportInfo.getType() == 0) {
//            //如果是第一种类型，查询要素列表做成表头
//            excelEntityList.add(elements.stream()
//                    .map(element -> new ExcelExportEntity(element.getName(),element.getName(),20))
//                    .collect(Collectors.toList()));
//        } else {
//            if (Tools.isNotEmpty(exportInfo.getTitle())) {
//                titles.addAll(Arrays.asList(exportInfo.getTitle().split(",")));
//            }
//            //第二种，根据数据库字段导出
//            Map<Integer,List<ExportField>> groupMap = ExportFieldService.getByTemplateId(templateId).stream().collect(Collectors.groupingBy(ExportField :: getExportGroup));
//            AssertUtil.isEmpty(groupMap,"导出失败，导出字段表对应场景的字段为空");
//            groupMap.forEach((group,exportFields) -> {
//                //设置列的键值对，还有宽度
//                List<ExcelExportEntity> excelExportEntities = new ArrayList<>(exportFields.size());
//                exportFields.forEach(exportField -> excelExportEntities.add(new ExcelExportEntity(exportField.getFieldName(),exportField.getFieldName(),exportField.getWidth())));
//                excelEntityList.add(excelExportEntities);
//            });
//        }
//
//    }


    //通过反射
    public static void resetCellMaxTextLength() {
        SpreadsheetVersion excel2007 = SpreadsheetVersion.EXCEL2007;
        if (Integer.MAX_VALUE != excel2007.getMaxTextLength()) {
            Field field;
            try {
                field = excel2007.getClass().getDeclaredField("_maxTextLength");
                field.setAccessible(true);
                field.set(excel2007, Integer.MAX_VALUE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean copyTaskContent(String taskSn, String newTaskSn) {
        //获取子场景的要素信息
        String templateSn = drawTaskInnerService.getTaskTemplate(newTaskSn);
        List<Element> elements = elementService.getElementsByTemplateSn(templateSn, 1);
        Map<Integer, Integer> elementIdMap = elements.stream().collect(Collectors.toMap(Element::getParentEleId, Element::getId));

        //查询已存在的要素信息
        QueryWrapper<ReviewContent> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("task_sn", taskSn).gt("version", -1);
        List<ReviewContent> reviewContents = reviewContentMapper.selectList(queryWrapper);
        //复制新的要素值
        reviewContents.forEach(reviewContent -> {
            reviewContent.setId(null);
            reviewContent.setParentEleId(reviewContent.getEleId());
            reviewContent.setTaskSn(newTaskSn);
            Integer elementId = elementIdMap.get(reviewContent.getEleId());
            reviewContent.setEleId(elementId);
        });

        this.saveBatch(reviewContents);
        //校验是否完成审核
        reviewOperationService.completeProductTask(Collections.singletonList(newTaskSn));

        return true;
    }


    @Override
    public String runScriptsTask(String taskSn) {
    // taskSn = drawTaskSn

        // start stop watch
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("preLoop");
        logger.info("======> start run runScriptsTask="+taskSn);
        // get JSONText
        Map<String, String> JSONTextMap = new HashMap<>();
        List<DrawTaskVO> childTaskList = fileTaskInnerService.getChildTasks(taskSn);
        if (Tools.isNotEmpty(childTaskList)) {
            String templateSn = childTaskList.get(0).getTemplateId();

            // get JSONText
            populateJSONTextMap(JSONTextMap, childTaskList, templateSn);

            stopWatch.stop();

            LambdaQueryWrapper<Element> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Element::getTemplateId, templateSn).eq(Element::getCategory, 1);
            List<Element> elements = elementService.list(queryWrapper);
            if (Tools.isNotEmpty(elements)) {

                // prepare related param 准备相关参数
                DependParamsVO dependParams = initDependParams(taskSn, JSONTextMap);

                List<SubProduct> subProducts = templateService.getSubProducts(templateSn);
                if (Tools.isNotEmpty(subProducts)) {
                    for (SubProduct subProduct : subProducts) {
                        dependParams.setSubProductId(subProduct.getId());
                        saveContentsByScripts(taskSn, JSONTextMap, templateSn, elements, dependParams);
                    }
                } else {
                    dependParams.setSubProductId(0);
                    saveContentsByScripts(taskSn, JSONTextMap, templateSn, elements, dependParams);
                }
            }
        }

        logger.info("======>run runScriptsTask end , taskSn ="+taskSn);
        return "ok";

    }


    private List<TaskElementVO.DrawResultVO> modelContents2drawResults(List<ModelContent> modelContents) {
        return modelContents.stream().map(mc -> {
            TaskElementVO.DrawResultVO drawResultVO = new TaskElementVO.DrawResultVO();
            BeanUtils.copyProperties(mc, drawResultVO);
            drawResultVO.setPos(new ArrayList<>());
            drawResultVO.setPara_info(CollectionUtil.str2listStr(mc.getParaInfo()));
            if (StringUtils.isNotEmpty(mc.getCusSampleId())) {
                drawResultVO.setCus_sample_id(mc.getCusSampleId());
            }
            if (mc.getGlobalPos() != null) {
                drawResultVO.setGlobal_pos(CollectionUtil.str2listInt(mc.getGlobalPos().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
            }
            if (mc.getPosInfo() != null) {
                drawResultVO.setPos_info(CollectionUtil.str2listInt(mc.getPosInfo().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
            }
            return drawResultVO;
        }).collect(Collectors.toList());
    }

    private void saveContentsByScripts(String taskSn, Map<String, String> JSONTextMap, String templateSn, List<Element> elements, DependParamsVO dependParams) {
        logger.info("======>start saveContentsByScripts ");
        for (Element element : elements) {

            List<ReviewContent> reviewContents = getContentsByScripts(taskSn, JSONTextMap, templateSn, dependParams, element);
            for (ReviewContent reviewContent : reviewContents) {
                DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
                //saveBatch(reviewContents);
                save(reviewContent);
            }
        }
        logger.info("======> save reviewContents data is ok ");
    }

    private List<ReviewContent> getContentsByScripts(String taskSn, Map<String, String> JSONTextMap, String templateSn, DependParamsVO dependParams, Element element) {
        List<ReviewContent> reviewContents = new ArrayList<>();

        Integer eleId = element.getId();
        String eleName = element.getName();

        DependParamsVO externalDependParams = populateExternalDependParams(taskSn, JSONTextMap, templateSn, dependParams, eleId, (DependParamsVO) ObjectUtils.deepClone(dependParams));

        String castName = element.getMapName() + "_" + eleId + "_" + element.getTemplateId();

        List<TaskElementVO.DrawResultVO> drawResultVOS = M2BEngine.filterNegativeElements(templateSn, castName, externalDependParams);
        externalDependParams.setJSONTextMap(new HashMap<>());
        drawResultVOS = M2BEngine.castModel2Business(element.getTemplateId(), castName, drawResultVOS, externalDependParams);
        if (Tools.isNotEmpty(drawResultVOS)) {
            TaskElementVO.DrawResultVO drawResultVO = drawResultVOS.get(0);
            // table
            if (drawResultVO.getIsTable() != null && drawResultVO.getIsTable().equals(TableType.IS_TABLE)) {
                List<TaskElementVO.DrawResultVO> rows = drawResultVO.getChildNodes();
                if (Tools.isNotEmpty(rows)) {
                    for (int i = 0; i < rows.size(); i++) {
                        if (rows.get(i) != null) {
                            List<TaskElementVO.DrawResultVO> childNodes = rows.get(i).getChildNodes();
                            if (Tools.isNotEmpty(childNodes)) {
                                String rowSequence = UUID.randomUUID().toString();
                                for (int j = 0; j < childNodes.size(); j++) {
                                    ReviewContent reviewContent = new ReviewContent();
                                    drawResult2reviewContent(childNodes.get(j), reviewContent);
                                    //reviewContent2drawResult(childNodes.get(j), reviewContent);
                                    reviewContent.setRowSequence(rowSequence);
                                    reviewContent.setFirstRow(i == 0 ? 1 : 0);
                                    rowSequence = UUID.randomUUID().toString();
                                    reviewContent.setNextRow(i == rows.size() - 1 ? "" : rowSequence);
                                    reviewContent.setColumnNo(j);
                                    reviewContent.setTaskSn(taskSn);
                                    reviewContent.setEleId(eleId);
                                    reviewContent.setEleName(eleName);
                                    reviewContent.setSubProductId(dependParams.getSubProductId());
                                    reviewContents.add(reviewContent);
                                }
                            }
                        }
                    }
                }
                // normal
            } else {
                String sequence = UUID.randomUUID().toString();
                for (int i = 0; i < drawResultVOS.size(); i++) {
                    TaskElementVO.DrawResultVO dr = drawResultVOS.get(i);
                    ReviewContent reviewContent = new ReviewContent();
                    drawResult2reviewContent(dr, reviewContent);
                    reviewContent.setSequence(sequence);
                    reviewContent.setFirstSequence(i == 0 ? 1 : 0);
                    sequence = UUID.randomUUID().toString();
                    reviewContent.setNextSequence(i == drawResultVOS.size() - 1 ? "" : sequence);
                    //reviewContent2drawResult(dr, reviewContent);
                    reviewContent.setTaskSn(taskSn);
                    reviewContent.setEleId(eleId);
                    reviewContent.setEleName(eleName);
                    reviewContent.setSubProductId(dependParams.getSubProductId());
                    reviewContents.add(reviewContent);
                }
                ;
            }
        } else {
            // empty/default
            ReviewContent reviewContent = ReviewContent.builder()
                    .businessValue("")
                    .text("")
                    .taskSn(taskSn)
                    .start(0)
                    .end(1)
                    .degree("0")
                    .endLineNo(0)
                    .paraInfo("[]")
                    .pos("[]")
                    .posInfo("[0,1]")
                    .startLineNo(0)
                    .sequence(UUID.randomUUID().toString())
                    .firstSequence(1)
                    .subProductId(0)
                    .nextSequence("")
                    .eleId(eleId)
                    .type(0)
                    .childTaskSn("").build();
            reviewContents.add(reviewContent);
        }

        return reviewContents;
    }

    private void reviewContent2drawResult(TaskElementVO.DrawResultVO drawResultVO, ReviewContent reviewContent) {
        BeanUtils.copyProperties(reviewContent, drawResultVO);
        drawResultVO.setPos(new ArrayList<>());
        drawResultVO.setPara_info(CollectionUtil.str2listStr(reviewContent.getParaInfo().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
        if (StringUtils.isNotEmpty(reviewContent.getGlobalPos())) {
            drawResultVO.setGlobal_pos(CollectionUtil.str2listInt(reviewContent.getGlobalPos().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
        }
        if (StringUtils.isNotEmpty(reviewContent.getPosInfo()) && !reviewContent.getPosInfo().equals("[]")) {
            drawResultVO.setPos_info(CollectionUtil.str2listInt(reviewContent.getPosInfo().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
        } else {
            drawResultVO.setPos_info(Arrays.asList(0, 0));
        }

    }

    private void modelContent2drawResult(TaskElementVO.DrawResultVO drawResultVO, ModelContent modelContent) {
        BeanUtils.copyProperties(modelContent, drawResultVO);
        drawResultVO.setPos(new ArrayList<>());
        drawResultVO.setPara_info(CollectionUtil.str2listStr(modelContent.getParaInfo().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
        if (StringUtils.isNotEmpty(modelContent.getGlobalPos())) {
            drawResultVO.setGlobal_pos(CollectionUtil.str2listInt(modelContent.getGlobalPos().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
        }
        drawResultVO.setPos_info(CollectionUtil.str2listInt(modelContent.getPosInfo().replaceAll("\\[", "").replaceAll("]", "").replaceAll(" ", "")));
    }

    private void drawResult2reviewContent(TaskElementVO.DrawResultVO drawResultVO, ReviewContent reviewContent) {
        BeanUtils.copyProperties(drawResultVO, reviewContent);
        if (StringUtils.isNotEmpty(reviewContent.getText()) && StringUtils.isEmpty(reviewContent.getBusinessValue())) {
            reviewContent.setBusinessValue(reviewContent.getText());
        }
        reviewContent.setPos("[]");
        if (Tools.isNotEmpty(drawResultVO.getPara_info())) {
            reviewContent.setParaInfo(drawResultVO.getPara_info().toString());
        } else {
            reviewContent.setParaInfo("[]");
        }

        if (drawResultVO.getGlobal_pos() != null && drawResultVO.getGlobal_pos().size() > 0) {
            reviewContent.setGlobalPos(drawResultVO.getGlobal_pos().toString());
        } else {
            reviewContent.setGlobalPos("[0,0]");
        }
        if (drawResultVO.getPos_info() != null && drawResultVO.getPos_info().size() > 0) {
            reviewContent.setPosInfo(drawResultVO.getPos_info().toString());
        } else {
            reviewContent.setPosInfo("[0,0]");
        }
    }


    private DependParamsVO populateExternalDependParams(String taskSn, Map<String, String> JSONTextMap, String templateSn, DependParamsVO dependParams, Integer eleId, DependParamsVO externalDependParams) {
        DependParamsVO innerDependParams = (DependParamsVO) ObjectUtils.deepClone(dependParams);
        assert innerDependParams != null;
        innerDependParams.setJSONTextMap(JSONTextMap);
        externalDependParams.setJSONTextMap(JSONTextMap);

        populateModelEleRelations(taskSn, eleId, externalDependParams);

        List<BusiBusiEleRelation> busiBusiEleRelations = busiBusiEleRelationService.list(new LambdaQueryWrapper<BusiBusiEleRelation>().eq(BusiBusiEleRelation::getEleId, eleId));
        if (Tools.isNotEmpty(busiBusiEleRelations)) {
            Map<String, List<TaskElementVO.DrawResultVO>> relatedBusiElements = new HashMap<>();
            busiBusiEleRelations.forEach(e -> {

                Integer dependEleId = e.getDependEleId();
                Element elementVO = elementService.getElementById(dependEleId);
                if (elementVO == null) {
                    return;
                }
                String busiCastName = elementVO.getMapName() + "_" + dependEleId + "_" + elementVO.getTemplateId();
                populateModelEleRelations(taskSn, dependEleId, innerDependParams);
                List<TaskElementVO.DrawResultVO> drawResultVOS = M2BEngine.filterNegativeElements(templateSn, busiCastName, innerDependParams);
                relatedBusiElements.put(elementVO.getName(), drawResultVOS);

            });
            externalDependParams.setRelatedBusiElements(relatedBusiElements);
        }
        return externalDependParams;
    }

    private DependParamsVO initDependParams(String taskSn, Map<String, String> JSONTextMap) {
        List<DrawTaskVO> childTasks = fileTaskInnerService.getChildTasks(taskSn);
        Map<String, String> rawTextPathMap = new HashMap<>();
        Map<String, String> JSONPathMap = new HashMap<>();
        Map<String, String> fileNames = new HashMap<>();
        Map<String, String> textMap = new HashMap<>();
        List<TableVO> tableVOList = new ArrayList<>();

        if (!CollectionUtils.isEmpty(childTasks)) {
            logger.info("==>initDependParams childTasks:" + childTasks.toString());
            for (DrawTaskVO childTask : childTasks) {
                String childTaskSn = childTask.getTaskSn();
                if (!StringUtils.isEmpty(childTaskSn) && !StringUtils.isEmpty(childTask.getTaskName())) {
                    String childTextPath = childTask.getCleanTextPath();
                    String childJSONPath = childTask.getJsonUrl();
                    String childTableJSONPath = childTask.getTableJsonUrl();
                    rawTextPathMap.put(childTaskSn, FDFS_FILE_DOMAIN + childTextPath);
                    JSONPathMap.put(childTaskSn, FDFS_FILE_DOMAIN + childJSONPath);
                    fileNames.put(childTaskSn, childTask.getTaskName());
                    textMap.put(childTaskSn,FileUtils.loadJson(FDFS_FILE_DOMAIN + childTextPath));
                    String tableJson = FileUtils.loadJson(FDFS_FILE_DOMAIN + childTableJSONPath);
                    if(StringUtils.isNotEmpty(tableJson)) {
                        List<ParseTableVO> tables = JSONArray.parseArray(tableJson, ParseTableVO.class);
                        TableVO tableVO = new TableVO();
                        tableVO.setFileName(childTask.getTaskName());
                        tableVO.setParseTableVO(tables);
                        tableVOList.add(tableVO);
                    }
                }
            }
        }

//        logger.info("==========> table: " + tableVOList);
        return DependParamsVO.builder()
                .textPathMap(rawTextPathMap)
                .textMap(textMap)
                .JSONPathMap(JSONPathMap)
                .fileNames(fileNames)
                .JSONTextMap(JSONTextMap)
                .tableVOList(tableVOList)
                .build();
    }

    private void populateModelEleRelations(String taskSn, Integer eleId, DependParamsVO dependParams) {
        List<BusiModelEleRelation> busiModelEleRelations = busiModelRelationService.list(new LambdaQueryWrapper<BusiModelEleRelation>().eq(BusiModelEleRelation::getEleId, eleId));
        if (Tools.isNotEmpty(busiModelEleRelations)) {
            Map<String, List<TaskElementVO.DrawResultVO>> relatedModelElements = new HashMap<>();
            busiModelEleRelations.forEach(e -> {
                Integer modelEleId = e.getModelEleId();
                DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
                LambdaQueryWrapper<ModelContent> modelQueryWrapper = new LambdaQueryWrapper<ModelContent>().eq(ModelContent::getModelEleId, modelEleId).eq(ModelContent::getTaskSn, taskSn);
                List<ModelContent> modelContents = modelContentService.list(modelQueryWrapper);
                if (Tools.isNotEmpty(modelContents)) {
                    List<TaskElementVO.DrawResultVO> drawResultVOS = modelContents2drawResults(modelContents);
                    Element elementDTO = elementService.getElementById(modelEleId);
                    relatedModelElements.put(elementDTO.getName(), drawResultVOS);
                }
            });

            dependParams.setRelatedModelElements(relatedModelElements);
        }
    }

    @Override
    public List<ReviewContent> countDistinctElements(String taskSn) {

        QueryWrapper<ReviewContent> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("DISTINCT id");
        queryWrapper.eq("parentTaskSn", taskSn);
        queryWrapper.eq("isDel", 0);
        return reviewContentMapper.selectList(queryWrapper);

    }

    @Override
    public TaskElementVO getModelElements(ElementSimpleSearchVO searchVO) {
        List<TaskElementVO.ElementVO> elementVOS = new ArrayList<>();
        String taskSn = searchVO.getTaskSn();
        String templateSn = drawTaskInnerService.getTaskTemplate(taskSn);

        PageHelper.startPage(searchVO.getPageIndex(), searchVO.getPageSize());
        LambdaQueryWrapper<Element> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Element::getTemplateId, templateSn)
                .eq(Element::getCategory, 1)
                .orderByAsc(Element::getId);
        if (Tools.isNotEmpty(searchVO.getSearchText())) {
            queryWrapper.like(Element::getName, searchVO.getSearchText());
        }

        List<Element> elements = elementService.list(queryWrapper);
        if (Tools.isNotEmpty(elements)) {
            for (Element element : elements) {
                TaskElementVO.ElementVO elementVO = TaskElementVO.ElementVO.builder().eleId(element.getId()).eleName(element.getName()).eleType(element.getType()).build();
                DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
                LambdaQueryWrapper<ModelContent> contentWrapper = new LambdaQueryWrapper<ModelContent>().eq(ModelContent::getTaskSn, taskSn)
                        .eq(ModelContent::getModelEleId, element.getId());
                DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
                List<ModelContent> contents = modelContentService.list(contentWrapper);
                if (Tools.isNotEmpty(contents)) {
                    elementVO.setResult(modelContent2DrawResultList(contents));
                } else {
                    TaskElementVO.DrawResultVO drawResultVO = TaskElementVO.DrawResultVO.builder().text("").build();
                    List<TaskElementVO.DrawResultVO> defaultContent = Collections.singletonList(drawResultVO);
                    elementVO.setResult(defaultContent);
                }
                elementVOS.add(elementVO);
            }
        }
        int count = elementService.count(queryWrapper);
        TaskElementVO result = new TaskElementVO(searchVO.getPageIndex(), searchVO.getPageSize(), count);


        result.setTaskSn(taskSn);
        result.setTotal(count);
        result.setElements(elementVOS);
        return result;

    }


    @Override
    public Map<Integer, List<TaskElementVO.DrawResultVO>> getTaskElementsScroll(ScrollSearchVO scrollSearchVO) {
        // 入参
        String taskSn = scrollSearchVO.getTaskSn();
        Map<Integer, String> eleSequenceMap = scrollSearchVO.getEleSequence();
        Integer batchSize = scrollSearchVO.getBatchSize();
        ArrayList<String> aimSeqList = new ArrayList<>(eleSequenceMap.values());

        // 动态获取表数据
        DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
        List<ReviewContent> reviewContents = lambdaQuery().eq(ReviewContent::getTaskSn, taskSn).list();
        Map<String, ReviewContent> seq2ReviewContentMap = reviewContents.stream().collect(Collectors.toMap(ReviewContent::getSequence, Function.identity()));

        // 构造seqLine
        Map<String, ArrayList<String>> lineMap = aimSeqList.stream()
                .collect(Collectors.toMap(
                        Function.identity(), seq -> {
                            ArrayList<String> seqList = new ArrayList<>();
                            ReviewContent curNode = seq2ReviewContentMap.get(seq);
                            seqList.add(seq);
                            while (curNode != null && seqList.size() < batchSize) {
                                seqList.add(curNode.getNextSequence());
                                curNode = seq2ReviewContentMap.get(curNode.getNextSequence());
                            }
                            return seqList;
                        }
                ));

        // 构造结果集
        Map<Integer, List<TaskElementVO.DrawResultVO>> result = eleSequenceMap.keySet().stream()
                .collect(Collectors.toMap(
                        Function.identity(), eleId -> {
                            List<String> currentLineList = lineMap.get(eleSequenceMap.get(eleId));
                            List<TaskElementVO.DrawResultVO> tmpResult = currentLineList.stream()
                                    .map(seq -> transfer2DrawResultVO(seq2ReviewContentMap.get(seq)))
                                    .collect(Collectors.toList());
                            return tmpResult;
                        }
                ));

        return result;
    }

    @Override
    public void exportElementsWord(String taskSn, String templateId, HttpServletResponse response) throws ExecutionException, InterruptedException {


        // 获取场景要素信息
        List<Element> elements = elementService.getElementsByTemplateId(templateId, 1);

        ExecutorService executorService = Executors.newFixedThreadPool(4);
        List<Future<Map<String, String>>> futureList = new ArrayList<>(4);

        Map<String, String> resultMap1 = new ConcurrentHashMap<>();

        if (Tools.isNotEmpty(elements)) {
            for (Element element : elements) {
                // 走完处理的结果对象
                Future<Map<String, String>> future = executorService.submit(() -> {
                    Map<String, String> resultMap = new HashMap<>();
                    DateTableNameParser.setTaskSn(Long.parseLong(taskSn));
                    List<ReviewContent> reviewContents = new LambdaQueryChainWrapper<>(reviewContentMapper)
                            .eq(ReviewContent::getTaskSn, taskSn)
                            .eq(ReviewContent::getEleId, element.getId())
                            .list();
                    List<TaskElementVO.DrawResultVO> drawResultVOS = transfer2DrawResultList(reviewContents, false);
                    if (Tools.isNotEmpty(drawResultVOS)) {
                        drawResultVOS.forEach(
                                d -> {
                                    if (StringUtils.isEmpty(d.getBusinessValue())) {
                                        d.setBusinessValue("   ");
                                    }
                                    resultMap.put("${" + element.getName() + "}", d.getBusinessValue());
                                }
                        );
                    }
                    return resultMap;
                });
                futureList.add(future);
            }
        }
        if (!CollectionUtils.isEmpty(futureList)) {
            for (Future<Map<String, String>> listFuture : futureList) {
                Map<String, String> locationResultMap = listFuture.get();
                resultMap1.putAll(locationResultMap);
            }
        }
        logger.info(">>>>>>>>>>>>>>>>>>>>>resultMap" + resultMap1.size());
        try {
            XWPFDocument document = new XWPFDocument(new BufferedInputStream(
                    new FileInputStream(TEMPLATE_PATH)));

            Iterator<XWPFParagraph> paragraphsIterator = document.getParagraphsIterator();
            while (paragraphsIterator.hasNext()) {
                XWPFParagraph next = paragraphsIterator.next();
                List<XWPFRun> runs = next.getRuns();

                int j = -1;
                for (int i = 0; i < runs.size(); i++) {

                    XWPFRun xwpfRun = runs.get(i);
                    String text = xwpfRun.getText(xwpfRun.getTextPosition());
                    if (text == null || text.trim().equals("")) {
                        continue;
                    }
                    if (text.equals("${")) {
                        text = "";
                        xwpfRun.setText(text, 0);
                        j = i + 1;
//                        continue;
                    } else if (text.equals("}")) {
                        text = "";
                        xwpfRun.setText(text, 0);
                    }
                    if (!text.equals("") && !text.contains("${") && !text.contains("}")) {
                        text = "${" + text + "}";
                    }
                    if (j == i) {
                        xwpfRun.setText(text, 0);
                        j = -1;
                        System.out.println(text);
                    }

                    //替换
                    for (Map.Entry<String, String> entry : resultMap1.entrySet()) {
                        String key = entry.getKey();
                        String value = entry.getValue();
                        if (text.contains(key)) {
                            text = text.replace(key, value);
                            xwpfRun.setText(text, 0);
                            break;
                        }
                    }
                    String xwpfRunText = xwpfRun.getText(xwpfRun.getTextPosition());
                    if (xwpfRunText.contains("${") || xwpfRunText.contains("}")) {
                        text = " ";
                        xwpfRun.setText(text, 0);
                    }
                }

            }
            XwpfTUtil.replaceInTable(document, resultMap1);
            Template template = templateService.getTemplateById(templateId);
            String fileName = template.getTemplateName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "utf-8") + "." + "docx");
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            OutputStream os = response.getOutputStream();
            document.write(os);
//            FileOutputStream fos = new FileOutputStream("D:\\tmp\\compare\\update\\首创证券备案材料生成.docx");
//            document.write(fos);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void populateJSONTextMap(Map<String, String> JSONTextMap, List<DrawTaskVO> childTaskList, String templateSn) {
        boolean needsJSON = false;
        try {
            needsJSON = templateService.needsJSON(templateSn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (Tools.isNotEmpty(childTaskList) && needsJSON) {
            int capacity = childTaskList.size();
            ExecutorService exec = Executors.newFixedThreadPool(capacity);
            CompletionService<Map<String, String>> service = new ExecutorCompletionService<>(exec);
            List<Future<Map<String, String>>> futures = new LinkedList<>();
            StopWatch sw = new StopWatch();
            sw.start("getJSONText");
            for (DrawTaskVO childTask : childTaskList) {
                if (!StringUtils.isEmpty(childTask.getTaskSn()) && !StringUtils.isEmpty(childTask.getTaskName())) {
                    Future<Map<String, String>> submit = service.submit(() -> getJSONText(childTask));
                    futures.add(submit);
                }
            }

            for (Future<Map<String, String>> future : futures) {
                try {
                    Map<String, String> singleJSONTextMap = future.get();
                    JSONTextMap.putAll(singleJSONTextMap);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            exec.shutdown();
            sw.stop();
            logger.info(sw.prettyPrint());
        }
    }

    @Override
    public TaskElementVO getTaskElements(ElementSimpleSearchVO searchVO) {

        StopWatch stopWatch = new StopWatch();
        stopWatch.start("preLoop");
        // 定义返回结果集合
        ResponseVO<List<FileTaskVo>> templateSnListResp = fileTaskInnerService.getTemplateIdListByDrawTaskSn(searchVO.getTaskSn());
        List<FileTaskVo> fileTaskVoList = templateSnListResp.getData();

        if (CollectionUtils.isEmpty(fileTaskVoList)) {
            throw new IllegalArgumentException("找不到该场景");
        }

        // 判断是否是混合场景
        boolean isMixedTemplate = fileTaskVoList.stream().anyMatch(f -> "0".equals(f.getParentFileTaskSn()));
        boolean isNB2Template = NbConstant.nbTemplateList.contains(fileTaskVoList.get(0).getTemplateId());

        if (!isMixedTemplate) {
            // 不是混合场景下
            // 相同的parent_task_sn(即template_id场景相同), 要进行去重, 留下template_id, parent_task_sn
            fileTaskVoList = fileTaskVoList.stream().filter(DuplicateUtil.distinctByKey(FileTaskVo::getTemplateId)).collect(Collectors.toList());
        } else {
            // 混合场景下, 过滤掉父场景, 因父场景没有要素
            fileTaskVoList = fileTaskVoList.stream().filter(f -> !"0".equals(f.getParentFileTaskSn())).collect(Collectors.toList());
        }

        if (searchVO.getPageIndex() != 1 && (isMixedTemplate || isNB2Template)) {
            TaskElementVO result = new TaskElementVO(searchVO.getPageIndex(), searchVO.getPageSize(), 0);

            result.setTaskSn(searchVO.getTaskSn());
            result.setGroups(null);
            result.setSubProductId(searchVO.getSubProductId());
            result.setMixedTemplate(isMixedTemplate);

            return result;
        }

        // 如果有传入templateId, 则只查询指定的templateId场景 (以便于根据要素分页)
        if (StringUtils.isNotEmpty(searchVO.getTemplateId())) {
            fileTaskVoList = fileTaskVoList.stream()
                    .filter(i -> i.getTemplateId().equals(searchVO.getTemplateId()))
                    .collect(Collectors.toList());
        }

        // fileTaskVoList进行分组, key是templateid, value是task_sn
        Map<String, List<FileTaskVo>> templateFileTaskVoMap = fileTaskVoList.stream().collect(Collectors.groupingBy(FileTaskVo::getTemplateId));

        Integer count = 0;
        // key: 场景id,  value: 每个drawResultVo的size
        Map<String, List<Integer>> drawResultVOSCountMap = new HashMap<>(16);

        List<TaskElementVO.GroupVO> groupVOs = new ArrayList<>();
        for (Map.Entry<String, List<FileTaskVo>> eachMap : templateFileTaskVoMap.entrySet()) {
            String templateSn = eachMap.getKey();

            List<String> taskSnList = eachMap.getValue().stream().map(FileTaskVo::getParentTaskSn).collect(Collectors.toList());

            Template template = templateService.getById(templateSn);

            List<Integer> privilegeElements = new ArrayList<>();
            if (Tools.isNotEmpty(Context.getTokenContext().getElements())) {
                Map<String, Set<Integer>> elements = Context.getTokenContext().getElements();
                if (Tools.isNotEmpty(elements.get(templateSn))) {
                    privilegeElements = new ArrayList<>(elements.get(templateSn));
                    // 过滤为权限要素集合中为null的值
                    privilegeElements = privilegeElements.stream().filter(privilegeElement -> {
                        if (privilegeElement != null) {
                            return true;
                        } else {
                            logger.info("privilegeElement == null, privilegeElements:" + privilegeElement);
                            return false;
                        }
                    }).collect(Collectors.toList());
                }
            }

            if (!isNB2Template) {
                PageHelper.startPage(searchVO.getPageIndex(), searchVO.getPageSize());
            }
            LambdaQueryWrapper<ElementGroup> queryWrapper = new LambdaQueryWrapper<>();
            if (privilegeElements.isEmpty()) {
                queryWrapper.eq(ElementGroup::getTemplateId, templateSn)
                        .orderByAsc(ElementGroup::getNo);
            } else {
                // 权限要素集合不为空时，在查询时增加in条件判断
                queryWrapper.eq(ElementGroup::getTemplateId, templateSn)
                        .in(ElementGroup::getEleIds, privilegeElements)
                        .orderByAsc(ElementGroup::getNo);
            }

            if (Tools.isNotEmpty(searchVO.getSearchText())) {
                queryWrapper.like(ElementGroup::getEleNames, searchVO.getSearchText());
            }

            List<ElementGroup> elementGroups = elementGroupService.list(queryWrapper);

            if (Tools.isNotEmpty(elementGroups)) {

                // get JSONText
                Map<String, String> JSONTextMap = new HashMap<>();
                String userCode = Context.getTokenContext().getUserCode();
                DependParamsVO dependParams = new DependParamsVO();
                if (StringUtils.isNotEmpty(userCode) && userCode.equals("test001")) {
                    List<DrawTaskVO> childTaskList = fileTaskInnerService.getChildTasks(searchVO.getTaskSn());
                    populateJSONTextMap(JSONTextMap, childTaskList, templateSn);
                    dependParams = initDependParams(searchVO.getTaskSn(), JSONTextMap);
                }

                // populate subProductId
                Integer subProductId = searchVO.getSubProductId();
                if (subProductId == 0) {
                    List<SubProduct> subProducts = templateService.getSubProducts(templateSn);
                    if (Tools.isNotEmpty(subProducts)) {
                        subProductId = subProducts.get(0).getId();
                    }
                }
                dependParams.setSubProductId(subProductId);

                // TODO batch sql once
             /*Set<Integer> eleIds = new HashSet<>();
             elementGroups.forEach((eg -> {
                String eleIdsStr = eg.getEleIds();
                if (eg.getType().equals(GroupType.SINGLE)) {
                    eleIds.add(Integer.parseInt(eleIdsStr));
                } else {
                    List<Integer> eldIdList = Arrays.stream(eg.getEleIds().split(",")).map(s -> Integer.parseInt(s.trim())).collect(Collectors.toList());
                    eleIds.addAll(eldIdList);
                }
            }));
            List<ReviewContent> reviewContentsCol = new LambdaQueryChainWrapper<>(reviewContentMapper).eq(ReviewContent::getTaskSn, taskSn).eq(ReviewContent::getEleId, eleIds)
                    .eq(ReviewContent::getSubProductId, subProductId).list();*/
                for (ElementGroup elementGroup : elementGroups) {

                    TaskElementVO.GroupVO groupVO = new TaskElementVO.GroupVO();
                    BeanUtils.copyProperties(elementGroup, groupVO);
                    groupVO.setTaskSn(searchVO.getTaskSn());
                    groupVO.setTemplateName(template.getTemplateName());
                    groupVO.setTemplateId(template.getTemplateSn());
                    //groupVO.setTaskName();
                    List<TaskElementVO.ElementVO> elementVOS = new ArrayList<>();

                    if (StringUtils.isNotEmpty(userCode) && userCode.equals("test001")) {

                        if (elementGroup.getType().equals(GroupType.SINGLE) && !(Tools.isNotEmpty(privilegeElements) && !privilegeElements.contains(Integer.parseInt(elementGroup.getEleIds())))) {

                            String eleId = elementGroup.getEleIds();
                            Element element = elementService.getElementById(Integer.parseInt(eleId));
                            String bindEleType = elementGroup.getBindEleType();
                            TaskElementVO.ElementVO elementVO = TaskElementVO.ElementVO.builder()
                                    .result(new ArrayList<>())
                                    .eleId(Integer.parseInt(eleId))
                                    .eleName(elementGroup.getEleNames())
                                    .eleType(bindEleType)
                                    .build();

                            List<ReviewContent> reviewContents = getContentsByScripts(searchVO.getTaskSn(), JSONTextMap, templateSn, dependParams, element);

                            if (Tools.isNotEmpty(reviewContents)) {
                                if (Tools.isNotEmpty(reviewContents.get(0).getRowSequence())) {
                                    // attribute row is not empty indicate belongs to a table
                                    populateTableResult(elementVO, reviewContents);
                                } else {
                                    // ele-type text
                                    List<TaskElementVO.DrawResultVO> drawResultVOS = transfer2DrawResultList(reviewContents, false);
                                    elementVO.setNo(element.getNo());
                                    elementVO.setResult(drawResultVOS);
                                }
                            }
                            elementVOS.add(elementVO);
                        } else {


                            if (drawResultVOSCountMap.get(templateSn) == null) {
                                drawResultVOSCountMap.put(templateSn, new ArrayList<>(10));
                            }

                            List<Integer> listIds = Arrays.stream(elementGroup.getEleIds().split(",")).map(s -> Integer.parseInt(s.trim())).collect(Collectors.toList());
                            for (Integer eid : listIds) {
                                if (!(Tools.isNotEmpty(privilegeElements) && !privilegeElements.contains(eid))) {
                                    Element element = elementService.getElementById(eid);
                                    TaskElementVO.ElementVO elementVO = TaskElementVO.ElementVO.builder().eleId(eid).eleName(element.getName()).eleType(element.getType()).build();
                                    List<ReviewContent> reviewContents = getContentsByScripts(searchVO.getTaskSn(), JSONTextMap, templateSn, dependParams, element);
                                    if (Tools.isNotEmpty(reviewContents)) {
                                        List<TaskElementVO.DrawResultVO> drawResultVOS = transfer2DrawResultList(reviewContents, false);
                                        elementVO.setResult(drawResultVOS);
                                        elementVO.setEleId(eid);
                                        elementVO.setNo(element.getNo());
                                        elementVO.setEleName(element.getName());
                                    }
                                    elementVOS.add(elementVO);

                                    if (!CollectionUtils.isEmpty(elementVO.getResult())) {
                                        drawResultVOSCountMap.get(templateSn).add(elementVO.getResult().size());
                                    }
                                }
                            }
                        }
                    } else {
                        // Single
                        if (elementGroup.getType().equals(GroupType.SINGLE) && !(Tools.isNotEmpty(privilegeElements) && !privilegeElements.contains(Integer.parseInt(elementGroup.getEleIds())))) {
                            String eleId = elementGroup.getEleIds();
                            String bindEleType = elementGroup.getBindEleType();
                            Element element = elementService.getById(eleId);
                            TaskElementVO.ElementVO elementVO = TaskElementVO.ElementVO.builder()
                                    .eleId(Integer.parseInt(eleId))
                                    .eleName(elementGroup.getEleNames())
                                    .eleType(bindEleType)
                                    .no(element.getNo())
                                    .build();

                            DateTableNameParser.setTaskSn(Long.parseLong(searchVO.getTaskSn()));
                            List<ReviewContent> reviewContents = new LambdaQueryChainWrapper<>(reviewContentMapper).in(ReviewContent::getTaskSn, taskSnList).eq(ReviewContent::getEleId, eleId)
                                    .eq(ReviewContent::getSubProductId, subProductId).list();

                            if (Tools.isNotEmpty(reviewContents)) {
                                // 相同子场景下， task_sn 不同， 所以根据task_sn区分不同子场景
                                // 对相同场景下的数据做处理， 业务需求： 可能某个场景下，只有一些要素是多个值，其他不存在多个值
                                String rowSequence = reviewContents.get(0).getRowSequence();
                                if (Tools.isNotEmpty(rowSequence)) {
                                    // attribute row is not empty indicate belongs to a table
                                    populateTableResult(elementVO, reviewContents);
                                    elementVO.setScroll(!isNB2Template && reviewContents.stream().filter(r -> r.getColumnNo().equals(0)).count() > 10);
                                } else {
                                    populateSingleEleVoByDb(elementVO, reviewContents, isNB2Template);
                                    elementVO.setScroll(!isNB2Template && reviewContents.size() > 10);
                                }

                            }
                            elementVOS.add(elementVO);
                            logger.info("====>要素信息：eleId=" + eleId + " 值信息elementVO=" + elementVO.getResult());
                        } else {
                            // Multi
                            // 定义对齐数据(用空值做填充对齐)
                            if (drawResultVOSCountMap.get(templateSn) == null) {
                                drawResultVOSCountMap.put(templateSn, new ArrayList<>(10));
                            }

                            List<Integer> listIds = Arrays.stream(elementGroup.getEleIds().split(",")).map(s -> Integer.parseInt(s.trim())).collect(Collectors.toList());

                            for (Integer eid : listIds) {
                                if (!(Tools.isNotEmpty(privilegeElements) && !privilegeElements.contains(eid))) {
                                    Element element = elementService.getElementById(eid);
                                    TaskElementVO.ElementVO elementVO = new TaskElementVO.ElementVO();
                                    if (null != element) {
                                        BeanUtils.copyProperties(element, elementVO);
                                        DateTableNameParser.setTaskSn(Long.parseLong(searchVO.getTaskSn()));
                                        List<ReviewContent> reviewContents = new LambdaQueryChainWrapper<>(reviewContentMapper).in(ReviewContent::getTaskSn, taskSnList).eq(ReviewContent::getEleId, eid)
                                                .eq(ReviewContent::getSubProductId, subProductId).list();

                                        populateSingleEleVoByDb(elementVO, reviewContents, isNB2Template);
                                        elementVO.setEleId(eid);
                                        elementVO.setEleName(element.getName());
                                        elementVO.setNo(element.getNo());
                                        elementVO.setScroll(!isNB2Template && reviewContents.size() > 10);
                                        elementVOS.add(elementVO);

                                        if (!CollectionUtils.isEmpty(elementVO.getResult())) {
                                            drawResultVOSCountMap.get(templateSn).add(elementVO.getResult().size());
                                        }
                                    }
                                }
                            }
                            logger.info("====>非 SINGLE 要素信息：listIds=" + listIds);
                        }
                    }

                    if (Tools.isNotEmpty(elementVOS)) {
                        groupVO.setResult(elementVOS);
                        groupVO.setScroll(elementVOS.get(0).isScroll());
                        groupVOs.add(groupVO);
                    }
                    // 增加标签类型处理 每个场景对应一个标签，逻辑按照成组模式写
                }

                count += elementGroupService.count(queryWrapper);
            }
        }

        Map<String, Integer> maxGroupCountMap = new HashMap<>(12);
        for (Map.Entry<String, List<Integer>> eachMap : drawResultVOSCountMap.entrySet()) {
            if (!CollectionUtils.isEmpty(eachMap.getValue())) {
                maxGroupCountMap.put(eachMap.getKey(), eachMap.getValue().stream().reduce(eachMap.getValue().get(0), Integer::max));
            }
        }


        // 没有数据的也显示表格横线, 由于前端无法处理, 故由后端做数据处理
        for (TaskElementVO.GroupVO groupVO : groupVOs) {
            // 过滤groupVo, maxGroupCountMap空值情况, 并且过滤非Multi, 非同一场景的情况
            boolean dataAlignmentFlag = !GroupType.MULTI.equals(groupVO.getType())
                    || CollectionUtils.isEmpty(groupVO.getResult())
                    || maxGroupCountMap.isEmpty()
                    || maxGroupCountMap.get(groupVO.getTemplateId()) == null;

            if (dataAlignmentFlag) {
                continue;
            }

            for (TaskElementVO.ElementVO elementVO : groupVO.getResult()) {
                int eachGroupSize = CollectionUtils.isEmpty(elementVO.getResult()) ? 0 : elementVO.getResult().size();
                boolean sizeFlag = !CollectionUtils.isEmpty(elementVO.getResult())
                        && eachGroupSize < maxGroupCountMap.get(groupVO.getTemplateId());

                // 由于怕误操作其他场景, 故只对此次宁波银行的场景做数据对齐的写库处理
                if (sizeFlag && NbConstant.nbTemplateList.contains(groupVO.getTemplateId())) {
                    for (; eachGroupSize < maxGroupCountMap.get(groupVO.getTemplateId()); eachGroupSize++) {
                        // 补充对齐的空数据, 同时写到数据库
                        TaskElementVO.DrawResultVO supplementalData = new TaskElementVO.DrawResultVO();
                        supplementalData.setText("");
                        supplementalData.setBusinessValue("");
                        supplementalData.setSequence(UUID.randomUUID().toString());
                        supplementalData.setSubProductId(0);
                        supplementalData.setNextSequence("");

                        boolean needAlignSameData = NbConstant.needAlignDataTemplateList.contains(groupVO.getTemplateId())
                                && NbConstant.needAlignDataElement.contains(elementVO.getEleName());

                        if (needAlignSameData) {
                            if (!CollectionUtils.isEmpty(elementVO.getResult())) {
                                supplementalData.setText(elementVO.getResult().get(0).getText());
                                supplementalData.setBusinessValue(elementVO.getResult().get(0).getBusinessValue());
                                supplementalData.setType(elementVO.getResult().get(0).getType());
                            }
                        }

                        ReviewContent reviewContent = new ReviewContent();
                        BeanUtils.copyProperties(supplementalData, reviewContent);
                        reviewContent.setEleId(elementVO.getEleId());
                        reviewContent.setTaskSn(groupVO.getTaskSn());
                        reviewContent.setGlobalPos("[0,0]");
                        reviewContent.setPosInfo("[0,0]");
                        reviewContent.setPos("[]");
                        reviewContent.setParaInfo("[]");
                        reviewContent.setEleName(elementVO.getEleName());

                        DateTableNameParser.setTaskSn(Long.parseLong(groupVO.getTaskSn()));
                        reviewContentMapper.insert(reviewContent);

                        elementVO.getResult().add(supplementalData);
                    }
                }
            }

        }


        TaskElementVO result = new TaskElementVO(searchVO.getPageIndex(), searchVO.getPageSize(), count);

        result.setTaskSn(searchVO.getTaskSn());
        result.setTotal(count);
        result.setGroups(groupVOs);
        result.setSubProductId(searchVO.getSubProductId());
        result.setMixedTemplate(isMixedTemplate);

        stopWatch.stop();
        logger.info(stopWatch.prettyPrint());

        return result;
    }

    private void populateSingleEleVoByDb(TaskElementVO.ElementVO elementVO, List<ReviewContent> reviewContents, boolean isNbTemplate) {

        if (Tools.isNotEmpty(reviewContents)) {
            List<TaskElementVO.DrawResultVO> drawResultVOS = new ArrayList<>();
            if (isNbTemplate) {
                // 宁波银行混合场景下, 不用做分页处理
                for (ReviewContent reviewContent : reviewContents) {
                    drawResultVOS.add(transfer2DrawResultVO(reviewContent));
                }
            } else {
                drawResultVOS = transfer2DrawResultList(reviewContents, true);
            }
            elementVO.setResult(drawResultVOS);
        } else {
            elementVO.setEleType(EleType.EMPTY);
        }
    }

    private void populateTableResult(TaskElementVO.ElementVO elementVO, List<ReviewContent> reviewContents) {
        if (Tools.isNotEmpty(reviewContents)) {

            List<TaskElementVO.DrawResultVO> drawResultVOS = new ArrayList<>();
            TaskElementVO.DrawResultVO drawResultVO = new TaskElementVO.DrawResultVO();
            List<TaskElementVO.DrawResultVO> rowNodes = new ArrayList<>();

            List<ReviewContent> firstRow = reviewContents.stream().filter(r -> r.getFirstRow() == 1).collect(Collectors.toList());
            if (Tools.isNotEmpty(firstRow)) {

                rowNodes.add(wrap2row(firstRow));
                String nextRow = firstRow.get(0).getNextRow();
                while (StringUtils.isNotEmpty(nextRow)) {
                    String finalNextRow = nextRow;
                    List<ReviewContent> collect = reviewContents.stream().filter(r -> r.getSequence().equals(finalNextRow)).collect(Collectors.toList());
                    if (Tools.isNotEmpty(collect)) {
                        rowNodes.add(wrap2row(collect));
                        nextRow = collect.get(0).getNextSequence();
                    } else {
                        break;
                    }
                }
            }
            drawResultVO.setChildNodes(rowNodes);
            drawResultVOS.add(drawResultVO);
            elementVO.setResult(drawResultVOS);
        } else {
            elementVO.setEleType(EleType.EMPTY);
        }
    }

    private TaskElementVO.DrawResultVO wrap2row(List<ReviewContent> firstRow) {
        TaskElementVO.DrawResultVO row = new TaskElementVO.DrawResultVO();
        firstRow.sort(Comparator.comparing(ReviewContent::getColumnNo));
        List<TaskElementVO.DrawResultVO> columnNodes = transfer2DrawResultList(firstRow, false);
        row.setChildNodes(columnNodes);
        return row;
    }

    private List<TaskElementVO.DrawResultVO> transfer2DrawResultList(List<ReviewContent> reviewContents, boolean numLimit) {
        List<TaskElementVO.DrawResultVO> result = new ArrayList<>();
        if (Tools.isNotEmpty(reviewContents)) {
            //String eleName = reviewContents.get(0).getEleName();
            //System.out.println(eleName);
            //ReviewContent first = reviewContents.stream().filter(r -> r.getFirstSequence() == 1).findFirst().get();
            List<ReviewContent> copyContents = (List<ReviewContent>) ObjectUtils.deepClone(reviewContents);
            ReviewContent first = new ReviewContent();
            for (ReviewContent reviewContent : copyContents) {
                if (reviewContent.getFirstSequence() != null && reviewContent.getFirstSequence() == 1) {
                    first = reviewContent;
                    copyContents.remove(reviewContent);
                    break;
                }
            }
            result.add(transfer2DrawResultVO(first));
            String nextSequence = first.getNextSequence();
            while (StringUtils.isNotEmpty(nextSequence)) {
                /*String finalNextSequence = nextSequence;
                List<ReviewContent> collect = reviewContents.stream().filter(r -> r.getSequence().equals(finalNextSequence)).collect(Collectors.toList());
                if(Tools.isNotEmpty(collect)){
                    ReviewContent next = collect.get(0);
                    result.add(transfer2DrawResultVO(next));
                    nextSequence = next.getNextSequence();
                }else{
                    break;
                }*/
                ReviewContent next = new ReviewContent();
                for (ReviewContent copyContent : copyContents) {
                    if (copyContent.getSequence() != null && copyContent.getSequence().equals(nextSequence)) {
                        next = copyContent;
                        copyContents.remove(copyContent);
                        break;
                    }
                }
                if (next.equals(new ReviewContent())) {
                    break;
                }
                result.add(transfer2DrawResultVO(next));
                if (numLimit && result.size() >= 10) {
                    break;
                }
                nextSequence = next.getNextSequence();
            }
        }
        return result;
    }

    private TaskElementVO.DrawResultVO transfer2DrawResultVO(ReviewContent rc) {
        TaskElementVO.DrawResultVO drawResultVO = new TaskElementVO.DrawResultVO();
        reviewContent2drawResult(drawResultVO, rc);
        return drawResultVO;
    }

    private List<TaskElementVO.DrawResultVO> modelContent2DrawResultList(List<ModelContent> modelContents) {
        return modelContents.stream().map(mc -> {
            TaskElementVO.DrawResultVO drawResultVO = new TaskElementVO.DrawResultVO();
            modelContent2drawResult(drawResultVO, mc);
            return drawResultVO;
        }).collect(Collectors.toList());
    }

    private Map<String, String> getJSONText(DrawTaskVO childTask) {
        Map<String, String> JSONTextMap = new HashMap<>();
        String childJSONPath;
        if (Tools.isNotEmpty(childTask.getJsonUrl())) {
            childJSONPath = childTask.getJsonUrl().replaceAll("media.tlrobot.com", "172.18.7.15:8111/");
        } else {
            return JSONTextMap;
        }

        String fileTextStr = FileUtils.loadJson(FDFS_FILE_DOMAIN + childJSONPath);
        JSONTextMap.put(childTask.getTaskSn(), fileTextStr);
        return JSONTextMap;
    }

}
